﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;

namespace J_Controle
{
    public class DialogoChar : ControladorDialogo
    {
        enum EstadoDialogo
        {
            Ini, // protocolo está no início da uma linha
            Tx,  // .. dentro da uma linha de caraters transmitidos
            Rx,  // .. de carateres recebidos
            Ex   // .. dentro da uma linha de aviso de condição excecional
        }

        EstadoDialogo Estado;
        TextBox Protocolo;
        StringBuilder ParteProtocolo;  // contribuição ao protocolo

        byte[] CharTx;
        byte[] CharRx;

        const int CapacBytesRx = 256;

        // =========================================
        //
        // Constructor
        //
        public DialogoChar(Panel prmPnl) : base(prmPnl)
        {
            CharTx = new byte[2];
            CharTx[1] = 0x0A; // em caso transmitir <CRLF>
            CharRx = new byte[CapacBytesRx];
            Protocolo = (TextBox)prmPnl.Controls["txtProtocolo"];
            ParteProtocolo = new StringBuilder();

            Estado = EstadoDialogo.Ini;
        }

        // =========================================
        //
        // Metodos para gerenciar o diálogo
        //
        public new bool Conectar(SerialPort prmPorta, ConfigHW prmCnf)
        {
            base.Conectar(prmPorta, prmCnf);
            try
            {
                PortaAtual.ReadTimeout = 20;  // 20 ms, independente do bps######################
                PortaAtual.WriteTimeout = 20;  // 20 ms, independente do bps######################
                PortaAtual.DataReceived += (hanDadosAtual = (SerialDataReceivedEventHandler)TransRx);
                PortaAtual.ErrorReceived += (hanErroAtual = (SerialErrorReceivedEventHandler)TransErroRx);
                PortaAtual.PinChanged += (hanPinoAtual = (SerialPinChangedEventHandler)TransPino);
                PortaAtual.Open();
                return true;
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Exceção inesperado em DialogoByte.Conectar");
                return false;
            }
        }


        // =========================================
        //
        // Métodos para transições do estado do diálogo
        public void TransTx(char prmChar)
        {
            string ParteProt = "";  // string para anexar no final do protocolo

            // transformar a representação
            int Codigo = (int)prmChar;
            CharTx[0] = (byte)Codigo;
            int NumBytes = (Codigo == 0x0D)? 2: 1;  // <CR> seria transmitido como <CR><LF>

            // fiscalizar se o carater fosse legítimo...
            bool legitimo = true;
            if (Codigo < 0x20) legitimo = false;
            if (Codigo > 0x7E) legitimo = false;
            if (Codigo == 0x0D) legitimo = true;
            if (Codigo == 0x0A) legitimo = true;
            if (!legitimo) return; // ...senão

            // Executar transição devido a transmissão incl. efeitos colaterais.
            // Bloqueiar exceptions
            try
            {
                // transição do estado + 
                // preparar contribuição para o protocolo, parte 1
                switch (Estado)
                {
                    case EstadoDialogo.Ini:
                        Estado = EstadoDialogo.Tx;
                        ParteProt += "-> ";
                        break;
                    case EstadoDialogo.Tx:
                        break; // só continuar
                    default:
                        Estado = EstadoDialogo.Tx;
                        ParteProt += "\r\n-> ";
                        break;
                }

                // gravação na porta
                PortaAtual.Write(CharTx, 0, NumBytes);

                // preparar contribuição para o protocolo, parte 2
                ParteProt += (Codigo == 0x0D) ? "\r\n" : Convert.ToString(prmChar);
            }
            catch (TimeoutException t)
            {
                TransTl(t);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Exceção inesperado em DialogoChars.TransTx()");
            }
            finally
            {
                // visualizar no protocolo (mesmo em caso de exceção!)
                AnxProt(ParteProt);
            }
        }
        public void TransRx(Object sender, SerialDataReceivedEventArgs d)
        {
            ParteProtocolo.Clear();
            int PosByte = 0;  // posição atual em CharRx;

            // Transição do estado devido ao byte recebido
            switch (Estado)
            {
                case EstadoDialogo.Ini:
                    Estado = EstadoDialogo.Rx;
                    ParteProtocolo.Append("<- ");
                    break;
                case EstadoDialogo.Rx:
                    break;  // só continuar
                default:
                    Estado = EstadoDialogo.Rx;
                    ParteProtocolo.Append("\r\n<- ");
                    break;
            }

 
            // ler bytes 
            // Podem chegar em proções. 
            // Podem exceder o tamanho do byte[] CharRx
            // podem causar exceptions e erros
            try
            {
                while ((PosByte < CapacBytesRx) & (PortaAtual.BytesToRead > 0))
                {
                    PosByte += PortaAtual.Read(CharRx, PosByte, CapacBytesRx - PosByte);
                }

                // testar se haja bytes
                if (PosByte == 0)
                {
                    // erro: com evento DataRead não tem dados
                    AnxProt(ParteProtocolo.ToString());
                    TransRx0(); 
                    return;
                }

                // concatenar para o protocolo
                for (int i = 0; i < PosByte; i++) ParteProtocolo.Append((char)CharRx[i]);
                AnxProt(ParteProtocolo.ToString());

                // evaluir final da linha
                if ((PosByte > 1) && (CharRx[PosByte - 2] == 0x0D) && (CharRx[PosByte - 1] == 0x0A))
                    Estado = EstadoDialogo.Ini;
                return; 
            }
            catch (TimeoutException t)
            {
                AnxProt(ParteProtocolo.ToString());
                TransTl(t);
                return;
            }
            catch (Exception e)
            {
                AnxProt(ParteProtocolo.ToString());
                MessageBox.Show(e.Message, "Exceção inesperado em DialogoChars.Transrx()");
                return;
            }
           
        }
        public void TransRx0()
        {
            // transição em caso 0 bytes recebidos depois evento DataRead
            // entraremos aqui somente se estado = Rx
            Estado = EstadoDialogo.Ex;
            AnxProt("\r\n## 0 bytes recebidos depois do evento DataRead.");
        }
        public void TransErroRx(Object sender, SerialErrorReceivedEventArgs e)
        {
            // transição em caso do evento ErrorRx
            Estado = EstadoDialogo.Ex;
            ParteProtocolo.Clear();
            ParteProtocolo.Append("\r\n## Erro na recepção de dados: ");
            ParteProtocolo.Append(e.EventType.ToString());
            AnxProt(ParteProtocolo.ToString());
        }
        public void TransPino(Object sender, SerialPinChangedEventArgs p)
        {
            // transição em caso do evento  PinChanged
            Estado = EstadoDialogo.Ex;
            ParteProtocolo.Clear();
            ParteProtocolo.Append("\r\n## Mudança no valor do pino: ");
            ParteProtocolo.Append(p.EventType.ToString());
            AnxProt(ParteProtocolo.ToString());
        }
        public void TransTl(TimeoutException t)
        {
            // transição em caso de tempo limite esgotado, na transmissão como na recepção
            
            ParteProtocolo.Clear();
            ParteProtocolo.Append("\r\n## Tempo limite esgotado em estado ");
            ParteProtocolo.Append(Estado.ToString());
            ParteProtocolo.Append(": ");
            ParteProtocolo.Append(t.Message);

            AnxProt(ParteProtocolo.ToString());

            Estado = EstadoDialogo.Ex;
        }

        // ======================================================
        //
        // métodos thread-safe para anexar info ao final do text box no painel
        //
        public void AnxProt(string prmTexto)
        {
            if (Protocolo.InvokeRequired) Protocolo.Invoke((Void_String)Protocolo.AppendText, prmTexto);
            else Protocolo.AppendText(prmTexto);
        }
        public void AnxProt(char prmTexto)
        {
            AnxProt(Convert.ToString(prmTexto));
        }
        public void AnxProt(byte prmTexto)
        {   
            //string ss = (prmTexto == 0x0D) ? "\r\n" : Convert.ToString(prmTexto);
            AnxProt(Convert.ToString(prmTexto));
        }

    }
}
